module control#(parameter I_WIDTH = 17, IA_WIDTH = 10, REG_SIZE = 4, D_WIDTH = 34, PA_WIDTH = 4, DA_WIDTH = 13, FIFO_WIDTH = 68, LDINST_WIDTH = 136, OPCODE_WIDTH = 5)
(
	input clk,
	input reset_i,
	input [I_WIDTH - 1 : 0] instruction_data_i,
	input instruction_valid_i,
	//DMEM refused
	input refused_i,
	//ALU branch bit
	input branch_i,
	
	// Datapath Outputs
    output is_jal_o,
	output is_dmem_o,
	output is_alu_o,
	output wen_o,
	output ldinst_valid_o,
	output is_computation_imm_o,
	output [OPCODE_WIDTH - 1 : 0] op_code_o,
	output [REG_SIZE - 1 : 0] wa_o,
	output [REG_SIZE - 1 : 0] ra0_o,
	output [REG_SIZE - 1 : 0] ra1_o,
	output is_sw_o,
	output is_swtch_o,
	output has_lab_or_imm_o,
	output uses_label_o,
	output is_swtch_or_jr_o,
	output is_staj_o,
	output is_staj_or_ldrsw_o,
	output is_in_o,
	output read_write_req_o,
	output write_en_o,
	output is_gic_o,
	
	output dequeue_o,
	output restart_o,
	
	//IO devices
	input in_ack_i,
	input out_ack_i,
	output in_req_o,
	output out_req_o,
	
	//instruction count
	output [32 - 1 : 0] instr_count_o
);

`define ADD    		17'b00000_????_????_????
`define ADDI			17'b00001_????_????_????
`define SUB    		17'b00010_????_????_????
`define SUBI			17'b00011_????_????_????
`define SRL    		17'b00100_????_????_????
`define NOR				17'b00101_????_????_????
`define LDINST    	17'b01000_????_????_????
`define INFLOOP		17'b01001_????_????_????
`define STAJ    		17'b01010_????_????_????
`define LDR				17'b01100_????_????_????
`define SW	    		17'b01101_????_????_????
`define IN				17'b10000_????_????_????
`define OUT    		17'b10001_????_????_????
`define LI				17'b10010_????_????_????
`define LIJ				17'b10011_????_????_????
`define J		    	17'b10100_????_????_????
`define JR				17'b10101_????_????_????
`define JAL				17'b10110_????_????_????
`define SWTCH    		17'b10111_????_????_????
`define BGT				17'b11000_????_????_????
`define BNE		    	17'b11001_????_????_????
`define GIC				17'b11110_????_????_????
`define HALT			17'b11111_????_????_????

reg reset_r;
reg [I_WIDTH - 1 : 0] instruction_data_r;
reg instruction_valid_r;

always_ff @(posedge clk)
	begin
		reset_r <= reset_i;
		instruction_data_r <= instruction_data_i;
		instruction_valid_r <= instruction_valid_i;
	end

reg [OPCODE_WIDTH - 1 : 0 ] op_code_next;
reg [REG_SIZE - 1 : 0] wa_next, ra0_next, ra1_next;

reg in_req_next, out_req_next;

reg in_ack_seen_r, out_ack_seen_r;

reg [31 : 0] instr_count;

reg is_lij;

// Parse instruction
always_comb
	begin
		unique casez (instruction_data_r)
			`LDINST:{op_code_next, ra1_next, ra0_next, wa_next} = instruction_data_r;
			`IN:    {op_code_next, ra0_next, ra1_next, wa_next} = instruction_data_r;
			`OUT:   {op_code_next, ra0_next, ra1_next, wa_next} = instruction_data_r;
			`SW:    {op_code_next, ra0_next, ra1_next, wa_next} = instruction_data_r;
			`STAJ:  {op_code_next, ra1_next, ra0_next, wa_next} = instruction_data_r;
			`BGT:   {op_code_next, ra0_next, ra1_next, wa_next} = instruction_data_r;
			`BNE:   {op_code_next, ra0_next, ra1_next, wa_next} = instruction_data_r;
         	`JR:    {op_code_next, ra0_next, ra1_next, wa_next} = instruction_data_r;
         	`LIJ: 
                begin
                    {op_code_next, wa_next, ra0_next, ra1_next} = instruction_data_r;
                    ra0_next = 4'b1110;
                end
			`SWTCH:
				begin
					{op_code_next, ra0_next, ra1_next, wa_next} = instruction_data_r;
					ra0_next = 4'b1;
				end
			default: {op_code_next, wa_next, ra0_next, ra1_next} = instruction_data_r;
		endcase
	end
	
reg [OPCODE_WIDTH - 1 : 0] op_code_r;

always_ff @(posedge clk)
	begin
		op_code_r <= op_code_next;
	end

assign op_code_o = op_code_r;
assign wa_o = wa_next;
assign ra0_o = ra0_next;
assign ra1_o = ra1_next;

	
reg is_jal_next, is_dmem_next, is_alu_next, is_gic_next, wen_next, ldinst_valid_next, is_computation_imm_next;
reg is_sw_next, is_swtch_next, has_lab_or_imm_next, uses_label_next, is_swtch_or_jr_next;
reg is_staj_or_ldrsw_next, is_staj_next, is_in_next, read_write_req_next, write_en_next;
reg restart_next;
reg is_halt_next;

reg is_jal_r, is_dmem_r, is_alu_r, is_gic_r, wen_r, ldinst_valid_r, is_computation_imm_r;
reg is_sw_r, is_swtch_r, has_lab_or_imm_r, uses_label_r, is_swtch_or_jr_r;
reg is_staj_or_ldrsw_r, is_staj_r, is_in_r, read_write_req_r, write_en_r;
reg is_halt_r;

reg is_dmem_access;

reg [1 : 0] counter;
reg is_clock_2, is_clock_3;

// is_jal
always_comb
    begin
        unique casez (instruction_data_r)
            `JAL:       is_jal_next = 1'b1;
            default:    is_jal_next = 1'b0;
        endcase
    end
	 
always_comb
	begin
		unique casez (instruction_data_r)
			`LIJ:		is_lij = 1'b1;
			default:	is_lij = 1'b0;
		endcase
	end

// is_dmem
always_comb
	begin
		unique casez (instruction_data_r)
			`LDINST: is_dmem_next = 1'b1;
			`LDR:		is_dmem_next = 1'b1;
			default:	is_dmem_next = 1'b0;
		endcase
	end
	
// is_dmem_access
always_comb
	begin
		unique casez (instruction_data_r)
			`LDINST: is_dmem_access = 1'b1;
			`LDR:		is_dmem_access = 1'b1;
			`SW:		is_dmem_access = 1'b1;
			`STAJ:	is_dmem_access = 1'b1;
			`IN:		is_dmem_access = 1'b1;
			default:	is_dmem_access = 1'b0;
		endcase
	end
	
// wen
always_comb
	begin
		unique casez (instruction_data_r)
			`ADD:		wen_next = 1'b1;
			`ADDI:	wen_next = 1'b1;
			`SUB:		wen_next = 1'b1;
			`SUBI:	wen_next = 1'b1;
			`SRL:		wen_next = 1'b1;
			`NOR:		wen_next = 1'b1;
			`LDINST:	wen_next = 1'b1;
			`LDR:		wen_next = 1'b1;
			`LI:		wen_next = 1'b1;
            `LIJ:       wen_next = 1'b1;
            `JAL:       wen_next = 1'b1;
			`GIC:		wen_next = 1'b1;
			default:	wen_next = 1'b0;
		endcase
	end
	
// ldinst_valid
always_comb
	begin
		unique casez (instruction_data_r)
			`LDINST: ldinst_valid_next = 1'b1;
			default:	ldinst_valid_next = 1'b0;
		endcase
	end

// is_alu
always_comb
	begin
		unique casez (instruction_data_r)
			`ADD:		is_alu_next = 1'b1;
			`ADDI:	is_alu_next = 1'b1;
			`SUB:		is_alu_next = 1'b1;
			`SUBI:	is_alu_next = 1'b1;
			`SRL:		is_alu_next = 1'b1;
			`NOR:		is_alu_next = 1'b1;
			default:	is_alu_next = 1'b0;
		endcase
	end
	
// is_gic
always_comb
	begin
		unique casez (instruction_data_r)
			`GIC:		is_gic_next = 1'b1;
			default:	is_gic_next = 1'b0;
		endcase
	end

// is_computation_imm
always_comb
	begin
		unique casez (instruction_data_r)
			`ADDI:	is_computation_imm_next = 1'b1;
			`SUBI:	is_computation_imm_next = 1'b1;
			`SRL:		is_computation_imm_next = 1'b1;
			`SW:		is_computation_imm_next = 1'b1;
			`BGT:		is_computation_imm_next = 1'b1;
			`BNE:		is_computation_imm_next = 1'b1;
			default:	is_computation_imm_next = 1'b0;
		endcase
	end
	
// is_sw
always_comb
	begin
		unique casez (instruction_data_r)
			`SW:		is_sw_next = 1'b1;
			default:	is_sw_next = 1'b0;
		endcase
	end
	
// is_staj_or_ldrsw
always_comb
	begin
		unique casez (instruction_data_r)
			`STAJ:	is_staj_or_ldrsw_next = 1'b1;
			//`LDINST:	is_staj_or_ldrsw_next = 1'b1;
			`IN:		is_staj_or_ldrsw_next = 1'b1;
			//`LDR:		is_staj_or_ldrsw_next = 1'b1;
			//`SW:		is_staj_or_ldrsw_next = 1'b1;
			default:	is_staj_or_ldrsw_next = 1'b0;
		endcase
	end
	
// is_staj
always_comb
	begin
		unique casez (instruction_data_r)
			`STAJ:	is_staj_next = 1'b1;
			default:	is_staj_next = 1'b0;
		endcase
	end
	
// is_in
always_comb
	begin
		unique casez (instruction_data_r)
			`IN:	is_in_next = 1'b1;
			default: is_in_next = 1'b0;
		endcase
	end

// is_swtch
always_comb
	begin
		unique casez (instruction_data_r)
			`SWTCH:	is_swtch_next = 1'b1;
			default:	is_swtch_next = 1'b0;
		endcase
	end
	
// uses_label
always_comb
	begin
		unique casez (instruction_data_r)
			`J:		    uses_label_next = 1'b1;
			`JAL:		uses_label_next = 1'b1;
			`BGT:		uses_label_next = 1'b1;
			`BNE:		uses_label_next = 1'b1;
			default:	uses_label_next = 1'b0;
		endcase
	end
	
// has_lab_or_imm
always_comb
	begin
		unique casez (instruction_data_r)
			`ADDI:	has_lab_or_imm_next = 1'b1;
			`SUBI:	has_lab_or_imm_next = 1'b1;
			`SRL:		has_lab_or_imm_next = 1'b1;
			`SW:		has_lab_or_imm_next = 1'b1;
			`LI:		has_lab_or_imm_next = 1'b1;
            `LIJ:       has_lab_or_imm_next = 1'b1;
			`J:		has_lab_or_imm_next = 1'b1;
			`JAL:		has_lab_or_imm_next = 1'b1;
			`BGT:		has_lab_or_imm_next = 1'b1;
			`BNE:		has_lab_or_imm_next = 1'b1;
			default:	has_lab_or_imm_next = 1'b0;
		endcase
	end
	
// is_swtch_or_jr
always_comb
	begin
		unique casez (instruction_data_r)
			`SWTCH:	is_swtch_or_jr_next = 1'b1;
			`JR:		is_swtch_or_jr_next = 1'b1;
            `LIJ:   is_swtch_or_jr_next = 1'b1;
			default:	is_swtch_or_jr_next = 1'b0;
		endcase
	end
    
// read_write_req
always_comb
	begin
		unique casez (instruction_data_r)
			`LDINST:	read_write_req_next = 1'b1;
			`STAJ:	read_write_req_next = 1'b1;
			`LDR:		read_write_req_next = 1'b1;
			`SW:		read_write_req_next = 1'b1;
			`IN:		read_write_req_next = 1'b1;
			default:	read_write_req_next = 1'b0;
		endcase
	end
	
// write_en
always_comb
	begin
		unique casez (instruction_data_r)
			`STAJ:	write_en_next = 1'b1;
			`SW:		write_en_next = 1'b1;
			`IN:		write_en_next = 1'b1;
			default:	write_en_next = 1'b0;
		endcase
	end

// restart_o
always_comb
    begin
        unique casez (instruction_data_r)
            `JR:
                begin
                if (instruction_valid_r)
                    restart_next = 1'b1;
                else
                    restart_next = ~branch_i | reset_r;
                end
            `LIJ:
                begin
                if (instruction_valid_r)
                    restart_next = 1'b1;
                else
                    restart_next = ~branch_i | reset_r;
                end
            default:             
                restart_next = ~branch_i;
        endcase
    end
	 
// In request
always_comb
	begin
		unique casez (instruction_data_r)
			`IN:
				in_req_next = 1'b1;
			default:
				in_req_next = 1'b0;
		endcase
	end

//in_ack_seen_r
always_ff @(posedge clk)
	begin
		if(in_ack_i & ~in_ack_seen_r)
			in_ack_seen_r <= in_ack_i;
		else if(counter == 2'b0)
			begin
				in_ack_seen_r <= 0;
			end
		else
			in_ack_seen_r <= in_ack_seen_r;
	end
	
// Out request
always_comb
	begin
		unique casez (instruction_data_r)
			`OUT:
				out_req_next = 1'b1;
			default:
				out_req_next = 1'b0;
		endcase
	end

//out_ack_seen_r
always_ff @(posedge clk)
	begin
		if(out_ack_i & ~out_ack_seen_r)
			out_ack_seen_r <= out_ack_i;
		else if(counter == 2'b0)
			begin
				out_ack_seen_r <= 0;
			end
		else
			out_ack_seen_r <= out_ack_seen_r;
	end

// Halt
always_comb
    begin
        unique casez (instruction_data_r)
            `HALT:
                is_halt_next = 1'b1;
            default:
                is_halt_next = 1'b0;
        endcase
    end
    
always_ff @(posedge clk)
	begin
		if(reset_r | ~instruction_valid_r)
			counter <= 2'b0;
		//Only 3 clock cycles
		else if(counter == 2'b10 & ~refused_i)
			begin
				counter <= 2'b0;
			end
		//Do not increment if dmem has refused or instruction is invalid
		//Or if in request and no acknowledge or out request and no acknowledeg
		else if(~refused_i & instruction_valid_r & ~(in_req_o & ~in_ack_i) & ~(out_req_o & ~out_ack_i))
			counter <= counter + 1;
	end

always_comb
	begin
		is_clock_2 = counter[0];
		is_clock_3 = counter[1];
	end
	
// instruction count incrementer
always_ff @(posedge clk)
	begin
		if(reset_i)
			instr_count <= 32'b0;
		else
			begin
				if(dequeue_o)
					instr_count <= instr_count + 1;
				else
					instr_count <= instr_count;
			end
	end
	
assign instr_count_o = instr_count;

always_ff @(posedge clk)
	begin
		is_swtch_r <= is_swtch_next;
		has_lab_or_imm_r <= has_lab_or_imm_next;
		uses_label_r <= uses_label_next;
		is_swtch_or_jr_r <= is_swtch_or_jr_next;
		is_computation_imm_r <= is_computation_imm_next & instruction_valid_r;
		is_gic_r <= is_gic_next;
		
		is_halt_r <= is_halt_next;
		
		// Clock 2 and 3
		is_sw_r <= is_sw_next & instruction_valid_r & (is_clock_2 | is_clock_3);
		is_staj_or_ldrsw_r <= is_staj_or_ldrsw_next & instruction_valid_r & (is_clock_2 | is_clock_3);
		is_staj_r <= is_staj_next & instruction_valid_r & (is_clock_2 | is_clock_3);
		is_in_r <= is_in_next & instruction_valid_r & (is_clock_2 | is_clock_3);
		read_write_req_r <= read_write_req_next & instruction_valid_r & (is_clock_2 | is_clock_3);
		write_en_r <= write_en_next & instruction_valid_r & (is_clock_2 | is_clock_3);
		
		// Clock 3
		is_jal_r <= is_jal_next & instruction_valid_r & is_clock_3;
		is_dmem_r <= is_dmem_next & instruction_valid_r & is_clock_3;
		is_alu_r <= is_alu_next & instruction_valid_r & is_clock_3;
		wen_r <= wen_next & (instruction_valid_r | is_lij) & is_clock_3;
		ldinst_valid_r <= ldinst_valid_next & instruction_valid_r & is_clock_3;
	end
	
// Clock all
assign is_swtch_o = is_swtch_r;
assign has_lab_or_imm_o = has_lab_or_imm_r;
assign uses_label_o = uses_label_r;
assign is_swtch_or_jr_o = is_swtch_or_jr_r;
assign is_computation_imm_o = is_computation_imm_r;
assign is_gic_o = is_gic_r;

// Clock 2 and 3
assign is_sw_o = is_sw_r;
assign is_staj_or_ldrsw_o = is_staj_or_ldrsw_r;
assign is_staj_o = is_staj_r;
assign is_in_o = is_in_r;
assign read_write_req_o = read_write_req_r;
assign write_en_o = write_en_r;

// Clock 3
assign is_jal_o = is_jal_r;
assign is_dmem_o = is_dmem_r;
assign is_alu_o = is_alu_r;
assign wen_o = wen_r;
assign ldinst_valid_o = ldinst_valid_r;

//dequeue on 3rd clock and there is a valid instruction waiting
assign restart_o = (restart_next & is_clock_3) | reset_r;
assign dequeue_o = counter[1] & instruction_valid_r & ~restart_o & ~(refused_i & is_dmem_access) & !is_halt_r;

//Request until ack comes in.
assign in_req_o = in_req_next & ~in_ack_i & ~in_ack_seen_r;
assign out_req_o = out_req_next & ~out_ack_i & ~out_ack_seen_r;

endmodule
